from typing import Optional
from urllib.parse import urlparse, parse_qs

from celery.exceptions import MaxRetriesExceededError
from celery.utils.log import get_task_logger
from requests import HTTPError
from monitor_tasks.celery import app
from monitor_tasks.table_storage import table_client
from monitor_tasks.utils import get_current_time
from baidubce.services.bts.model import Row, Cell, ScanArgs
from onething_client import DeviceManager
from onething_client import DeviceListRequest

logger = get_task_logger(__name__)


def get_query_params(url: str) -> dict:
    try:
        parsed_url = urlparse(url)
        return parse_qs(parsed_url.query)
    except Exception as e:
        raise Exception("Error while parsing the url. Detail: {}".format(e))


def construct_rowkey(userid: str, sn: str):
    return f"{userid[::-1]}{sn}"


@app.task(bind=True, retries=3)
def acquire_device_authorization_info(self, device_info, userid: str, rk: str, ak: str):
    try:
        sn = device_info['sn']
        response = get_device_info(ak, rk, sn, userid)
        handle_response(ak, device_info, response, rk, sn, userid)
    except (HTTPError, KeyError, Exception) as ex:
        handle_error(self, ex)


def handle_response(ak, device_info, response, rk, sn, userid):
    if 'iRet' in response and response['iRet'] == 16012:
        raise Exception("Connection failed, please try again later!")
    if 'data' not in response or 'url' not in response['data']:
        raise KeyError(response)
    query_params = get_query_params(response['data']['url'])
    save_device_info(ak, device_info, query_params, rk, sn, userid)


def save_device_info(ak, device_info, query_params, rk, sn, userid):
    row = Row()
    row.rowkey = construct_rowkey(userid, sn)
    row.cells.extend(create_device_info_cells(ak, query_params, rk, userid))
    device_info_row = convert_device_info_to_bce_row(device_info)
    row.cells.extend(device_info_row.cells)
    table_client.put_row(b'flow_guardian', b'device_info', row)


def get_device_info(ak, rk, sn, userid):
    dlr = DeviceListRequest(sessionid=ak, userid=userid, rk=rk, ak=ak)
    dm = DeviceManager(dlr)
    response = dm.generate_url(sn)
    logger.info(response)
    return response


def handle_error(task_instance, ex: Exception):
    if isinstance(ex, HTTPError):
        logger.error(ex)
    elif isinstance(ex, KeyError):
        logger.error(f"Key error occurred: {ex}")
    else:
        try:
            raise task_instance.retry(exc=ex, countdown=10)
        except MaxRetriesExceededError:
            logger.error(f"Max retries exceeded: {ex}")


def create_device_info_cells(ak, query_params, rk, userid):
    return [
        Cell('userid', str(userid)).__dict__,
        Cell('sign', query_params.get('sign', {})[0]).__dict__,
        Cell('expire', str(query_params.get('expire', {})[0])).__dict__,
        Cell('rk', rk).__dict__,
        Cell('ak', ak).__dict__,
        Cell('last_modify', get_current_time()).__dict__
    ]


_DEVICE_INFO_FIELDS = [
    'bizType',
    'deviceGroup',
    'deviceRemark',
    'deviceStatus',
    'deviceType',
    'isp',
    'province',
    'recruitStatus',
    'recruitStatusText',
    'sn'
]


def convert_device_info_to_bce_row(device_info):
    row = Row()
    for key in _DEVICE_INFO_FIELDS:
        row.cells.append(Cell(key, str(device_info[key])).__dict__)

    return row


@app.task
def scan_all_user_token():
    scan_args = ScanArgs()
    response = table_client.scan(b'flow_guardian', b'user_token_info', scan_args)
    for row in response.result:
        user_token_info = {}
        for cell in row.cells:
            user_token_info.update({cell.column: cell.value})
        query_device_list.delay(user_token_info['userid'], user_token_info['rk'], user_token_info['ak'])


@app.task
def query_device_list(userid: str, rk: str, ak: str, device_number: Optional[str] = None):
    device_list_request = DeviceListRequest(sessionid=ak, userid=userid, rk=rk, ak=ak)
    dm = DeviceManager(device_list_request)
    try:
        response = dm.device_list(fuzzyQuery=device_number)
        logger.info(response)
        if 'sMsg' not in response or 'deviceInfoList' not in response['data']:
            raise KeyError(f"request body:\n {device_list_request.model_dump()}\nresponse body:{response}")
        for device_info in response['data']['deviceInfoList']:
            if device_info['deviceStatus'] == 1:
                acquire_device_authorization_info.delay(device_info, userid, rk, ak)
    except (HTTPError, KeyError, Exception) as ex:
        logger.error(ex)
